package common.utils;

import lombok.var;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;

public class ReflectUtil {

    public static <T extends Annotation> T getAttribute(Class clazz, Class<T> annotationClazz) {
        return (T) clazz.getDeclaredAnnotation(annotationClazz);
    }

    public static <T extends Annotation> T getAttribute(Field field, Class<T> annotationClazz) {
        return field.getAnnotation(annotationClazz);
    }

    public static Class getClass(Object object) {
        return object.getClass();
    }

    public static Field[] getProperties(Class clazz) {
        //return clazz.getDeclaredFields();//这个不行，继承的属性丢了
        List<Field> fieldList = new ArrayList<>();
        while (clazz != null) {
            fieldList.addAll(new ArrayList<>(Arrays.asList(clazz.getDeclaredFields())));
            clazz = clazz.getSuperclass();
        }
        Field[] fields = new Field[fieldList.size()];
        fieldList.toArray(fields);
        return fields;
    }

    public static Field[] getFields(Class clazz) {
        return getProperties(clazz);
    }

    public static Field[] getProperties(Object object) {
        return getProperties(getClass(object));
    }

    public static Field getField(Class clazz, String propertyName) {
        //return clazz.getDeclaredField(propertyName);//这个不行，继承的属性丢了?未验证
        var fields = getProperties(clazz);
        return LinqUtil.filterOne(fields, m -> m.getName().equals(propertyName));
    }

    public static Field getField(Object object, String propertyName) {
        return getField(getClass(object), propertyName);
    }

    public static Field getField(List<?> items, String propertyName) {
        return getField(items.get(0), propertyName);
    }

    public static boolean hasProperty(Class clazz, String propertyName) {
        return getField(clazz, propertyName) != null;
    }

    public static boolean hasProperty(Object object, String propertyName) {
        return getField(object, propertyName) != null;
    }

    public static boolean hasId(Object entity) {
        return hasProperty(entity, "id");
    }

    public static boolean hasNum(Class clszz) {
        return hasProperty(clszz, "num");
    }

    public static boolean hasNum(Object entity) {
        return hasProperty(entity, "num");
    }

    public static <T> boolean hasId(List<T> entities) {
        return hasId(entities.get(0));
    }

    public static <T> boolean hasNum(List<T> entities) {
        return hasNum(entities.get(0));
    }

    public static Object getValue(Object object, Field field) {
        try {
            field.setAccessible(true);
            var value = field.get(object);
            field.setAccessible(false);
            return value;
        } catch (Exception ex) {
            //ex.printStackTrace();
            return null;
        }
    }

    public static Object getValue(Object object, String propertyName) {
        return getValue(object, getField(object, propertyName));
    }

    public static String getId(Object entity) {
        return ConvertUtil.toString(getValue(entity, "id"));
    }

    public static long getNum(Object entity) {
        return ConvertUtil.toLong(getValue(entity, "num"));
    }

    public static boolean isValid(Object valid) {
        //我们Entity中isValid定义的是小写的int,如果有这属性默认就是0,不会是null
        //如果是null就是实体类中没这个属性,认为其有效
        return valid == null || valid.toString().equals("1");
    }

    public static <T> T getValid(T entity) {
        var valid = getValue(entity, "isValid");
        if (isValid(valid))
            return entity;
        return null;
    }

    public static <T> LinkedList<Object> getValues(List<T> objects, Field field) {
        try {
            if (objects.size() == 0)
                return new LinkedList<>();
            field.setAccessible(true);
            var values = new LinkedList();
            for (var object : objects)
                values.add(field.get(object));
            field.setAccessible(false);
            return values;
        } catch (Exception ex) {
            //ex.printStackTrace();
            return null;
        }
    }

    public static <T> LinkedList<Object> getValues(List<T> objects, String propertyName) {
        return getValues(objects, getField(objects, propertyName));
    }

    public static <T> LinkedList<String> getIds(List<T> entities) {
        return ArrayUtil.toStringList(getValues(entities, "id"));
    }

    public static <T> LinkedList<Long> getNums(List<T> entities) {
        return ArrayUtil.toLongList(getValues(entities, "num"));
    }

    public static void setProperty(Object object, Field field, Object propertyValue) {
        try {
            field.setAccessible(true);
            field.set(object, propertyValue);
            field.setAccessible(false);
        } catch (Exception ex) {
            //ex.printStackTrace();
        }
    }

    public static void setProperty(Object object, String propertyName, Object propertyValue) {
        setProperty(object, getField(object, propertyName), propertyValue);
    }

    public static void setNum(Object object, Long propertyValue) {
        setProperty(object, "num", propertyValue);
    }

    public static <T> void setProperties(List<T> objects, String propertyName, Object propertyValue) {
        try {
            var field = getField(objects.get(0), propertyName);
            field.setAccessible(true);
            for (var object : objects)
                field.set(object, propertyValue);
            field.setAccessible(false);
        } catch (Exception ex) {
            //ex.printStackTrace();
        }
    }

    public static <T, R> void setProperties(List<T> items, FieldExpression<? super T, ? extends R> mapExpression, Object value) {
        setProperties(items, mapExpression.getFieldName(), value);
    }

    public static <T> void setIncreaseProperties(List<T> objects, String propertyName, long propertyValue) {
        try {
            var field = getField(objects.get(0), propertyName);
            field.setAccessible(true);
            for (var object : objects)
                field.set(object, propertyValue++);
            field.setAccessible(false);
        } catch (Exception ex) {
            //ex.printStackTrace();
        }
    }

    public static <T> void setIncreaseNums(List<T> objects, long propertyValue) {
        setIncreaseProperties(objects, "num", propertyValue);
    }

    public static <T1, T2> T2 copyProperties(T1 from, T2 to, String... ignores) {
        var fromFields = getProperties(from);
        var toFields = getProperties(to);
        for (var fromFiled : fromFields) {
            var toFiled = LinqUtil.filterOne(toFields, m -> m.getName().equals(fromFiled.getName()));
            if (toFiled == null || StringUtil.contains(ignores, toFiled.getName()))
                continue;
            fromFiled.setAccessible(true);
            toFiled.setAccessible(true);
            try {
                toFiled.set(to, fromFiled.get(from));
            } catch (Exception ex) {
                ex.printStackTrace();
            }
            fromFiled.setAccessible(false);
            toFiled.setAccessible(false);
        }
        return to;
    }

    /**
     * item = ReflectionUtil.clone(item, new Item());
     */
    public static <T> T clone(T from, T to) {
        return copyProperties(from, to);
    }

    public static <T1, T2, R1, R2> void copyProperties(
            List<T1> fromList, List<T2> toList,
            FieldExpression<? super T1, ? extends R1> fromMatch,
            FieldExpression<? super T2, ? extends R2> toMatch,
            FieldExpression<? super T1, ? extends R1> copyFrom,
            FieldExpression<? super T2, ? extends R2> copyTo) {
        for (var to : toList) {
            var from = LinqUtil.filterOne(fromList, m -> fromMatch.apply(m).equals(toMatch.apply(to)));
            if (from == null)
                continue;
            setProperty(to, copyTo.getFieldName(), copyFrom.apply(from));
        }
    }

    public static Class getArgumentsGeneric(Class clazz, int index) {
        var type = clazz.getGenericSuperclass();
        if (type instanceof ParameterizedType) {
            var parameterizedType = (ParameterizedType) type;
            return (Class) parameterizedType.getActualTypeArguments()[index];
        }
        throw new RuntimeException();
    }

}
